在 compile time 的時候,不像我們一般再開發的時候很容易的去 log 一些我們要的資訊,這邊我們必須要透過 processor 提供的 Messager
才能進行 log ,這個 log 的內容會顯示在 IDE 的 build console 內。
我們也可以自己用一個 class 去封 Messager
,像是這樣:
class Logger(private val log: Messager) {
fun log(msg: String) {
log.printMessage(Diagnostic.Kind.NOTE, "$msg\r\n")
}
fun warn(msg: String) {
log.printMessage(Diagnostic.Kind.WARNING, "$msg\r\n")
}
fun error(msg: String, element: Element) {
log.printMessage(Diagnostic.Kind.ERROR, "$msg\r\n", element)
}
}
之後就可以直接使用方法來 log 不同層級的資訊!
Extension 是幫助我們在產生 parser 程式碼的過程中會用到的一些工具,我們可以從前幾篇在實作 DOM parser 的時候,有用到的東西去做規劃。
readString
getAttribute
getElementByTag
toBoolean
於是我們可以知道我們想要產生的類別大概長怎樣,我們就可以先在編輯器先寫上想產生的類別:
object ParserExtensions {
fun Element.readString(name: String, parentTag: String? = null): String? {
val nodeList = getElementsByTagName(name)
if (parentTag == null) {
return nodeList.item(0)?.textContent
} else {
for (i in 0 until nodeList.length) {
val e = nodeList.item(i) as? Element ?: continue
val parent = e.parentNode as? Element
if (parent?.tagName != parentTag) continue
return e.textContent
}
return null
}
}
fun Element.getAttributeOrNull(tag: String): String? {
val attr = getAttribute(tag) ?: return null
return if (attr.isEmpty() || attr.isBlank()) null else attr
}
fun Element.getElementByTag(tag: String): Element? {
val nodeList = getElementsByTagName(tag)
if (nodeList.length == 0) return null
return nodeList.item(0) as? Element
}
fun String.toBooleanOrNull(): Boolean? = when (toLowerCase()) {
"true", "yes" -> true
"no", "false" -> false
else -> null
}
}
程式碼的細節可以參考前幾篇的 "使用 DOM Parser" ,裡面有針對 Element
的介紹,這邊就不再贅述。接下來就可以勾勒出 KotlinExtensionGenerator
的大概結構:
class KotlinExtensionGenerator(
private val logger: Logger
) : ExtensionGenerator() {
private val elementClass = ClassName("org.w3c.dom", "Element")
override fun generate(): FileSpec {
logger.log("Generating $EXTENSION_NAME for Kotlin.")
return FileSpec.builder(GENERATOR_PACKAGE,EXTENSION_NAME)
.addType(
TypeSpec.objectBuilder(EXTENSION_NAME)
.addFunction(getReadStringFunSpec())
.addFunction(getAttributeOrNullFunSpec())
.addFunction(getElementByTagFunSpec())
.addFunction(getBooleanConversionFunSpec())
.build()
)
.build()
}
private fun getReadStringFunSpec() {
// 略
}
private fun getAttributeOrNullFunSpec() {
// 略
}
private fun getElementByTagFunSpec() {
// 略
}
}
generate 的方法裡主要定義了 ParserExtension
會有幾個方法與它的型態,這邊是定義成 object 。裡面的 fun spec 就比較沒什麼特別的,都可以用簡單的 KotlinPoet 語法完成,如果有興趣的朋友可以參考這邊。這個類別還有一個 getBooleanConversionFunSpec
還沒有被提到,因為它是父類別 ExtensionGenerator
的方法,主要是產生 boolean 值轉換的方法,對應到 ParserExtension
裡的 toBooleanOrNull
方法。這個抽象類別同時也被 ExtensionGenerator
繼承。
abstract class ExtensionGenerator : Generator {
protected fun getBooleanConversionFunSpec() = FunSpec.builder(METHOD_TO_BOOLEAN)
.receiver(String::class)
.addCode(
"""
|return when (toLowerCase()) {
|${TAB}"true", "yes" -> true
|${TAB}"no", "false" -> false
|${TAB}else -> null
|}
""".trimMargin()
)
.returns(Boolean::class.asTypeName().copy(nullable = true))
.build()
}
實作到這裡,我們可以產生一些 extension 在 parser 的 generator 裡面可以用,下篇我們會提到以 DOM parser 為基底產生的 parser 。